package com.syspeak.modules.spring.web.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts2.json.JSONException;
import org.apache.struts2.json.JSONUtil;
import org.apache.struts2.json.SerializationParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.syspeak.modules.spring.web.servlet.mvc.annotation.JsonConfig;
import com.syspeak.modules.web.springmvc.BaseControllerImpl;

public class JsonLibViewInterceptor implements HandlerInterceptor {

	private final Logger logger = LoggerFactory.getLogger(getClass());
	private String defaultEncoding = "UTF-8";

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		logger.info("preHandle: " + request + ", " + handler);
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		if (modelAndView == null) {
			return;
		}
		if (handler instanceof HandlerMethod) {
			HandlerMethod handlerMethod = (HandlerMethod) handler;
			Method method = handlerMethod.getMethod();
			Object returnValue = modelAndView.getModel().get(BaseControllerImpl.PAGE_BEAN);
			if (returnValue == null) {
				return;
			}
			NativeWebRequest webRequest = new ServletWebRequest(request, response);
			handleJsonResponse(method, null, returnValue, null, webRequest);
			//throw new ReturnNullViewException("Need View return Null!");
		}
		logger.info("postHandle: " + request + ", " + handler);
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		logger.info("afterCompletion: " + request + ", " + handler);
	}

	private void handleJsonResponse(Method handlerMethod, Class<?> handlerType, Object returnValue,
			ExtendedModelMap implicitModel, NativeWebRequest webRequest) {
		JsonConfig jsonConfig = AnnotationUtils.findAnnotation(handlerMethod, JsonConfig.class);
		if (jsonConfig != null) {
			String includeProperties = jsonConfig.includeProperties();
			String excludeProperties = jsonConfig.excludeProperties();
			String[] ignoreProperties = jsonConfig.ignoreProperties();
			boolean excludeNullProperties = jsonConfig.excludeNullProperties();
			//----
			boolean ignoreHierarchy = true;
			boolean enumAsBean = false;
			boolean wrapWithComments = false;
			boolean prefix = false;
			boolean enableSMD = false;
			boolean enableGZIP = false;
			boolean noCache = false;
			String callbackParameter;
			String contentType = null;
			;
			String wrapPrefix = null;
			;
			String wrapSuffix = null;
			;
			//----
			int statusCode = jsonConfig.statusCode();
			int errorCode = jsonConfig.errorCode();

			PrintWriter writer = null;

			try {
				String json = JSONUtil.serialize((Object) returnValue, this.getExcludeProperties(excludeProperties),
						this.getIncludeProperties(includeProperties), ignoreHierarchy, enumAsBean,
						excludeNullProperties);

				boolean writeGzip = enableGZIP
						&& JSONUtil.isGzipInRequest(((ServletRequestAttributes) webRequest).getRequest());

				JSONUtil.writeJSONToResponse(new SerializationParams(((ServletWebRequest) webRequest).getResponse(),
						getEncoding(), wrapWithComments, json, false, writeGzip, noCache, statusCode, errorCode,
						prefix, contentType, wrapPrefix, wrapSuffix));
				//				writer = ((ServletWebRequest)webRequest).getResponse().getWriter();
				//				writer.write(json);
				//				writer.flush();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (JSONException e) {
				e.printStackTrace();
				throw new RuntimeException();
			} finally {
				if (writer != null) {
					writer.close();
				}
			}
		}

	}

	/**
	 * @param includedProperties
	 *            the includeProperties to set
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public List<Pattern> getIncludeProperties(String commaDelim) {
		Set<String> includePatterns = JSONUtil.asSet(commaDelim);
		if (includePatterns != null) {
			List<Pattern> includeProperties = new ArrayList<Pattern>(includePatterns.size());

			HashMap existingPatterns = new HashMap();

			for (String pattern : includePatterns) {
				// Compile a pattern for each *unique* "level" of the object
				// hierarchy specified in the regex.
				String[] patternPieces = pattern.split("\\\\\\.");

				String patternExpr = "";
				for (String patternPiece : patternPieces) {
					if (patternExpr.length() > 0) {
						patternExpr += "\\.";
					}
					patternExpr += patternPiece;

					// Check for duplicate patterns so that there is no overlap.
					if (!existingPatterns.containsKey(patternExpr)) {
						existingPatterns.put(patternExpr, patternExpr);

						// Add a pattern that does not have the indexed property
						// matching (ie. list\[\d+\] becomes list).
						if (patternPiece.endsWith("\\]")) {
							includeProperties.add(Pattern.compile(patternExpr.substring(0,
									patternPiece.lastIndexOf("\\["))));

							if (logger.isDebugEnabled())
								logger.debug("Adding include property expression:  "
										+ patternExpr.substring(0, patternPiece.lastIndexOf("\\[")));
						}

						includeProperties.add(Pattern.compile(patternExpr));

						if (logger.isDebugEnabled())
							logger.debug("Adding include property expression:  " + patternExpr);

						return includeProperties;
					}
				}
			}
		}

		return null;
	}

	public List<Pattern> getExcludeProperties(String commaDelim) {
		Set<String> excludePatterns = JSONUtil.asSet(commaDelim);
		if (excludePatterns != null) {
			List<Pattern> excludeProperties = new ArrayList<Pattern>(excludePatterns.size());
			for (String pattern : excludePatterns) {
				excludeProperties.add(Pattern.compile(pattern));
			}
			return excludeProperties;
		}
		return null;
	}

	protected String getEncoding() {
		String encoding = this.defaultEncoding;

		if (encoding == null) {
			encoding = System.getProperty("file.encoding");
		}

		if (encoding == null) {
			encoding = "UTF-8";
		}

		return encoding;
	}
}
